package com.example.thread;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author hej
 * @date 2022/10/19 16:50
 * 同样是阻塞、唤醒的方法，park/unpark 和 wait/notify，await/signal有什么区别？
 */
public class WaitCompare {

    public static void main(String[] args) {
        test1();
        test2();
        test3();
    }

    /**
     * Object类中的wait和notify方法实现线程等待和唤醒
     * 结论：
     * wait和notify方法必须要在同步块或者方法里面且成对出现使用，否则会抛出java.lang.IllegalMonitorStateException。
     * 调用顺序要先wait后notify才OK
     */
    public static void test1() {
        Object object = new Object();
        new Thread(() -> {
            synchronized (object) {
                System.out.println(Thread.currentThread().getName() + " 进入");
                try {
                    TimeUnit.SECONDS.sleep(3);
                    object.wait();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + " 唤醒");
        }, "Thread A").start();

        new Thread(() -> {
            synchronized (object) {
                object.notify();
                System.out.println(Thread.currentThread().getName() + " 通知");
            }
        }, "Thread B").start();
    }

    /**
     * Lock锁Condation中的await和singnal方法实现线程等待和唤醒
     * 结论：
     * await和singnal必须要在同步块或者方法里面且成对出现使用，否则会抛出java.lang.IllegalMonitorStateException。
     * 调用顺序要先await后singnal才可以
     */
    public static void test2() {
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + " 进入");
                lock.lock();
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
            System.out.println(Thread.currentThread().getName() + " 唤醒");
        }, "Thread X").start();

        new Thread(() -> {
            try {
                lock.lock();
                condition.signal();
                System.out.println(Thread.currentThread().getName() + " 通知");
            } finally {
                lock.unlock();
            }
        }, "Thread Y").start();
    }

    /**
     * LockSupport的park/unpark方法实现线程等待和唤醒
     */
    public static void test3() {
        /*
                LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
                LockSupport类使用了一种名为Permit（许可）的概念来做到阻塞和唤醒线程的功能，每个线程都有一个许可（permit），
            permit只有两个值1和零，默认是零。可以把许可看成是一种(0.1)信号量（Semaphore），但与Semaphore不同的是，
            许可的累加上限是1。

                也就是说，调用LockSupport的park方法时，需要消耗一张许可证，如果没有获取到许可证（也就是permit的值为0）时，
            就会一直阻塞，等待许可证来进行消耗,如果有许可证可消耗，就会停止阻塞。调用LockSupport的unpark方法，
            就是生产一份许可证（permint的值加一，由原来的0变为1）。而且许可证的的累加上限是1。

                线程阻塞需要消耗凭证(permit)，这个凭证最多只有1个。当调用park方法时，如果有凭证，
            则会直接消耗掉这个凭证然后正常退出。如果无凭证，就必须阻塞等待凭证可用。
                而unpark则相反，它会增加一个凭证，但凭证最多只能有1个，累加无效。
         */
        Thread a = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 进入");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + " 唤醒");
        }, "Thread 1");
        a.start();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            LockSupport.unpark(a);
            System.out.println(Thread.currentThread().getName() + " 通知");
        }, "Thread 2").start();
    }
}
